package com.bwstudio.demo.gateway.filter;

import java.util.Arrays;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import com.alibaba.fastjson.JSON;
import com.bwstudio.common.jwt.exception.TokenAuthenticationException;
import com.bwstudio.common.jwt.response.ResponseCodeEnum;
import com.bwstudio.common.jwt.util.JWTUtil;
import com.bwstudio.common.response.IResponseCode;
import com.bwstudio.common.response.Result;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Slf4j
@ConfigurationProperties("my.jwt")
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {

	@Getter
	@Setter
	private String[] skipAuthUrls;
	
	@Value("${secretKey:123456}")
	private String secretKey;

	 @Autowired
	 private StringRedisTemplate stringRedisTemplate;

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		ServerHttpRequest serverHttpRequest = exchange.getRequest();
		ServerHttpResponse serverHttpResponse = exchange.getResponse();
		String uri = serverHttpRequest.getURI().getPath();

		// 检查白名单（配置）
		// 方法一, 在配置文件中进行配置
		if(null != skipAuthUrls && Arrays.asList(skipAuthUrls).contains(uri)) {
			return chain.filter(exchange);
		}
		
		// 方法二, 写死在代码中
//		if (uri.indexOf("/auth-service/login") >= 0) {
//			return chain.filter(exchange);
//		}

		String token = serverHttpRequest.getHeaders().getFirst("token");
		if (StringUtils.isBlank(token)) {
			serverHttpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
			return getVoidMono(serverHttpResponse, ResponseCodeEnum.TOKEN_MISSION);
		}

		/**
		 *  JWT token在过期之前是不会失效的，所以如果多次登录的话，
		 *  虽然redis中可能只存放了最新的一个token，但是前边分发的token如果保存在客户端，在过期之前客户端仍然可以用已存的token进行访问
		 *  这样的方案，无法在服务端控制客户的访问
		 */
		try {
			JWTUtil.verifyToken(token, secretKey);
		} catch (TokenAuthenticationException ex) {
			return getVoidMono(serverHttpResponse, ResponseCodeEnum.TOKEN_INVALID);
		} catch (Exception ex) {
			return getVoidMono(serverHttpResponse, ResponseCodeEnum.UNKNOWN_ERROR);
		}

		/**
		 *  检查Redis中是否有此Token 然后对比redis中的token和前端传入的token是否一致，
		 *  这样可以保证只有redis中存放的token有访问权限，前边已分发非最新的token一律作废，
		 *  即后边登录的用户会导致前边登录到 用户会话失效
		 */
		String username = JWTUtil.getUsername(token);
		HashOperations<String, String, String> hashOperations = stringRedisTemplate.opsForHash();
		String key = username;
		String tokenInRedis = hashOperations.get(key, "token");
		if (StringUtils.isBlank(tokenInRedis) || !tokenInRedis.equals(token)) {
			return getVoidMono(serverHttpResponse, ResponseCodeEnum.TOKEN_INVALID);
		}
		
//		String userId = JWTUtil.getUserInfo(token);

		ServerHttpRequest mutableReq = serverHttpRequest.mutate().header("Authorization-UserName", username).build();
		ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();

		return chain.filter(mutableExchange);
	}

	private Mono<Void> getVoidMono(ServerHttpResponse serverHttpResponse, IResponseCode responseCode) {
		serverHttpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
		serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
		Result result = Result.failure(responseCode);
		DataBuffer dataBuffer = serverHttpResponse.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
		return serverHttpResponse.writeWith(Flux.just(dataBuffer));
	}

	@Override
	public int getOrder() {
		return -100;
	}
}
